package com.linln.admin.system.service.ql;

import cn.hutool.core.util.ClassUtil;
import com.linln.admin.system.controller.ScheduleController;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.IExpressContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * QL规则组件
 */
@Slf4j
@Service
public class RuleComponent {
    @Resource
    private ApplicationContext ctx;
    private static ExpressRunner runner = new ExpressRunner();

    private static final List<ScheduleController.MethodVo> functions = new ArrayList<>();

    //默认20s
    private static Long DEFULT_TIME_OUT = 20_000L;

    private static AtomicBoolean isLoaded = new AtomicBoolean(false);

    /**
     * 获取定义的所有基础包
     *
     * @return
     */
    public static List<String> getRootPackageNames() throws Exception {
        return (List<String>) FieldUtils.readField(runner.getRootExpressPackage(), "m_packages", true);
    }

    public static List<ScheduleController.MethodVo> getFunctions() {
        return functions;
    }

    @PostConstruct
    public void setUp() throws Exception {
        if (isLoaded.compareAndSet(false, true)) {
            Map<String, QlRule> beans = ctx.getBeansOfType(QlRule.class);
            //初始化方法 传入
            for (QlRule ruleBean : beans.values()) {
                Method[] methods = ClassUtil.getPublicMethods(ruleBean.getClass());
                for (Method method : methods) {
                    RuleMethod rm = method.getAnnotation(RuleMethod.class);
                    if (rm == null) {
                        continue;
                    }
                    runner.addFunctionOfServiceMethod(method.getName(), ruleBean, method.getName(), method.getParameterTypes(), null);
                    ScheduleController.MethodVo methodVo = new ScheduleController.MethodVo();
                    methodVo.setType("public");
                    methodVo.setDesc(method.getName() + "(" + getArgTypes(method) + ")\n" + rm.description());//方法注释
                    methodVo.setResultType(method.getReturnType().getName());//方法返回值
                    methodVo.setResultTypeClass(method.getReturnType());
                    methodVo.setVarName(method.getName());
                    methodVo.setParams(ScheduleController.getMethodParameterNames(method));
                    functions.add(methodVo);
                }
            }
            //把常用工具类放入
            runner.getRootExpressPackage().addPackage("com.alibaba.fastjson");
            runner.getRootExpressPackage().addPackage("cn.hutool.http");
            //

        }
    }

    private String getArgTypes(Method method) {
        StringBuilder argTypes = new StringBuilder();
        if (method.getParameterTypes() != null && method.getParameterTypes().length > 0) {
            for (Class arg : method.getParameterTypes()) {
                if (argTypes.length() > 0) {
                    argTypes.append(", ");
                }
                argTypes.append(arg.getName());
            }
        }
        return argTypes.toString();
    }

    private IExpressContext createContext(Map<String, Object> paramMap) throws Exception {
        IExpressContext<String, Object> expressContext = new DefaultContext<String, Object>();
        //默认传入日志
        expressContext.put("log", log);

        //自定义入参 传入
        if (null != paramMap) {
            for (String key : paramMap.keySet()) {
                expressContext.put(key, paramMap.get(key));
            }
        }
        return expressContext;
    }

    /**
     * 执行规则（默认超时时间）
     *
     * @param paramMap
     * @param express
     * @return
     * @throws Exception
     */
    public Object execute(Map<String, Object> paramMap, String express) throws Exception {
        return execute(paramMap, express, DEFULT_TIME_OUT);
    }

    /**
     * 执行规则
     *
     * @param paramMap
     * @param express
     * @param timeout  超时毫秒时间
     * @return
     * @throws Exception
     */
    public Object execute(Map<String, Object> paramMap, String express, long timeout) throws Exception {
        IExpressContext expressContext = createContext(paramMap);
//        long startTime = System.currentTimeMillis();
        Object result = runner.execute(express, expressContext, null, true, true, timeout);
//        log.info("RuleUtil execute success, costs time:{}", (System.currentTimeMillis() - startTime) + "ms");
        return result;
    }
}
